﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections;
using System.Diagnostics.Contracts;

namespace AppToolkit.WPF.ComponentModel
{
    /// <summary>
    /// Supports tracking changes of items implementing <see cref="INotifyPropertyChanged"/>.
    /// </summary>
    /// <example><code><![CDATA[
    /// SimpleComponent testComp = new SimpleComponent();
    /// ChangeTracker target = new ChangeTracker(testComp);
    /// 
    /// testComp.TestProperty = "changed";
    /// Assert.IsTrue(target.HasChanges);
    /// ]]></code></example>
    public class ChangeTracker : IDisposable
    {
        private INotifyPropertyChanged _trackedComponent;

        private ChangeTracker()
        {
            _propChangedHandler = new PropertyChangedEventHandler(OnPropertyChanged);
        }

        /// <summary>
        /// Hook up with component.
        /// </summary>
        /// <param name="component"></param>
        public ChangeTracker(INotifyPropertyChanged component) 
            : this()
        {
            Contract.Requires<ArgumentNullException>(component != null, "component");
            _trackedComponent = component;

            hookChangeNotifications(_trackedComponent, true);
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (_trackedComponent != null)
            {
                hookChangeNotifications(_trackedComponent, false);
                _trackedComponent = null;
            }
        }
        #endregion

        #region Public API
        private bool _hasChanges;
        /// <summary>
        /// Returns true, if the tracked object tree has been modified since the tracker has been attached
        /// or resetted (<see cref="Reset"></see>).
        /// </summary>
        public bool HasChanges
        {
            get
            {
                CheckDisposed();
                return _hasChanges;
            }
            private set
            {
                _hasChanges = value;
            }
        }

        /// <summary>
        /// Resets HasChanges state to false.
        /// </summary>
        public void Reset()
        {
            CheckDisposed();

            this.HasChanges = false;
        }
        #endregion

        private void CheckDisposed()
        {
            if (_trackedComponent == null)
                throw new ObjectDisposedException("ChangeTracker");
        }

        private void hookChangeNotifications(System.Collections.IList items, bool hook)
        {
            foreach (var item in items)
            {
                INotifyPropertyChanged comp = item as INotifyPropertyChanged;
                if (null != comp)
                {
                    hookChangeNotifications(comp, hook);
                }
            }
        }

        private void hookChangeNotifications(INotifyPropertyChanged obj, bool hook)
        {
//#if TRACE_CHANGE_TRACKING
            System.Diagnostics.Trace.WriteLine( string.Format("{0} -> {1}", obj.GetType().Name, hook));
//#endif
            if (hook)
                obj.PropertyChanged += _propChangedHandler;
            else
                obj.PropertyChanged -= _propChangedHandler;


            System.Reflection.PropertyInfo[] pi = obj.GetType().GetProperties();
            foreach (var item in pi)
            {
                if (typeof(INotifyCollectionChanged).IsAssignableFrom(item.PropertyType))
                {
                    INotifyCollectionChanged iface = (INotifyCollectionChanged)item.GetValue(obj, null);
                    if (hook)
                        iface.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
                    else
                        iface.CollectionChanged -= new NotifyCollectionChangedEventHandler(OnCollectionChanged);

                    IList coll = iface as IList;
                    if (null != coll && coll.Count > 0)
                    {
                        hookChangeNotifications(coll, hook);
                    }
                }
                else if (typeof(IBindingList).IsAssignableFrom(item.PropertyType))
                {
                    IBindingList iface = (IBindingList)item.GetValue(obj, null);
                    if (hook)
                        iface.ListChanged += new ListChangedEventHandler(OnListChanged);
                    else
                        iface.ListChanged -= new ListChangedEventHandler(OnListChanged);
                }
            }
        }

        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.HasChanges = true;
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    hookChangeNotifications(e.NewItems, true);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    hookChangeNotifications(e.OldItems, false);
                    break;
                default:
                    break;
            }
        }

        private void OnListChanged(object sender, ListChangedEventArgs e)
        {
            this.HasChanges = true;
        }

        private PropertyChangedEventHandler _propChangedHandler;

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            this.HasChanges = true;
        }
    }

    //public class CollectionChangeTracker
    //{
    //    private INotifyCollectionChanged _collection;

    //    public CollectionChangeTracker(INotifyCollectionChanged collection)
    //    {
    //        Contract.ThrowIfNull(collection, "collection");
    //        _collection = collection;
    //    }

    //    void iface_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    //    {
    //        switch (e.Action)
    //        {
    //            case NotifyCollectionChangedAction.Add:
    //                hookPropertyChanged(e.NewItems, true);
    //                break;
    //            case NotifyCollectionChangedAction.Remove:
    //                hookPropertyChanged(e.OldItems, false);
    //                break;
    //            default:
    //                break;
    //        }
    //    }
    //}
}
